/*******************************************************************************
 * Copyright 2008 www.softao.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.softao.ming.gwt.user.client;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.user.client.ui.HasFocus;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;

/**
 * Defines an unit for validation.
 * <p>
 * The {@link #createFailMessageWidget()} method returns the default label, with
 * the <code>css</code> style name as: <code>ming-validationFailMessage</code>.
 * 
 * @author Dop Sun Jul 13, 2008
 */
public final class ValidationUnit {
	private static class ValidationCell {
		public Widget mWidget;

		public ValidationRule mRule;

		public String mFailMessage;
	}

	private HasText mFailMessageWidget;

	private ValidationFailCallback mFailCallback;

	private final List<ValidationCell> mRules;

	private boolean mFocusOnFail;

	/**
	 * Initializes a new instance of <code>ValidationUnit</code>.
	 */
	public ValidationUnit() {
		this.mFocusOnFail = true;
		this.mRules = new ArrayList<ValidationCell>();
	}

	/**
	 * @return widget for displaying messages if the inputs can not pass the
	 *         validation
	 */
	public final HasText getFailMessageWidget() {
		return mFailMessageWidget;
	}

	/**
	 * @param widget
	 *            the widget to validate
	 * @param rule
	 *            the rule for validation
	 * @param failMessage
	 *            message if validation failed
	 */
	public final void addRule(Widget widget, ValidationRule rule,
			String failMessage) {
		ValidationCell cell = new ValidationCell();
		cell.mWidget = widget;
		cell.mRule = rule;
		cell.mFailMessage = failMessage;
		this.mRules.add(cell);
	}

	/**
	 * Validates all widgets in this unit.
	 * 
	 * @return <code>true</code> if all widgets passed the validation;
	 *         otherwise, <code>false</code>.
	 */
	public final boolean validate() {
		if (this.mFailMessageWidget != null) {
			this.mFailMessageWidget.setText("");
		}
		for (ValidationCell cell : this.mRules) {
			if (!cell.mRule.execute(cell.mWidget)) {
				this.reportFailMessage(cell);
				return false;
			}
		}
		return true;
	}

	private final void reportFailMessage(ValidationCell cell) {
		if (this.mFailMessageWidget != null) {
			this.mFailMessageWidget.setText(cell.mFailMessage);
			if (this.mFocusOnFail) {
				if (cell.mWidget instanceof HasFocus) {
					FocusHelper.setFocus((HasFocus)cell.mWidget);
				}
			}
		}
		if (this.mFailCallback != null) {
			this.mFailCallback.onFail(cell.mWidget, cell.mRule,
					cell.mFailMessage);
		}
	}

	/**
	 * Validates the specified <code>widget</code> in this unit. If the widget
	 * has not been added yet, return <code>true</code>.
	 * 
	 * @param widget
	 *            the widget to validate
	 * @return <code>true</code> if the <code>widget</code> passed the
	 *         validation; otherwise, <code>false</code>.
	 */
	public final boolean validate(Widget widget) {
		return false;
	}

	/**
	 * @param failMessageWidget
	 *            widget for displaying messages if the inputs can not pass the
	 *            validation
	 */
	public final void setFailMessageWidget(HasText failMessageWidget) {
		mFailMessageWidget = failMessageWidget;
	}

	/**
	 * @param failCallback
	 *            the failCallback to set
	 */
	public final void setFailCallback(ValidationFailCallback failCallback) {
		mFailCallback = failCallback;
	}

	/**
	 * @return the failCallback
	 */
	public final ValidationFailCallback getFailCallback() {
		return mFailCallback;
	}

	/**
	 * Creates the default fail message widget and assigned to the
	 * <code>FailMessageWidget</code>.
	 * <p>
	 * The CSS style name is: <code>ming-validationFailMessage</code>;
	 * 
	 * @return the default fail message widget
	 */
	public final Label createFailMessageWidget() {
		Label lblText = new Label();
		lblText.setStyleName("ming-validationFailMessage");
		this.mFailMessageWidget = lblText;
		return lblText;
	}

	/**
	 * @param focusOnFail
	 *            <code>true</code> the widget will be focused if validation
	 *            failed; otherwise, <code>true</code>. Default is
	 *            <code>true</code>.
	 */
	public final void setFocusOnFail(boolean focusOnFail) {
		mFocusOnFail = focusOnFail;
	}

	/**
	 * @return <code>true</code> the widget will be focused if validation
	 *         failed; otherwise, <code>true</code>. Default is
	 *         <code>true</code>.
	 */
	public final boolean isFocusOnFail() {
		return mFocusOnFail;
	}
}
